#include <iostream>
#include "errno.h"
#include "Conexion.h"

using namespace std;
using namespace TP::Comunicacion;

#define ERROR_CONEXION -1

Conexion::Conexion(const string& pPuerto, IManejadorConexionEvento* pManejador) {
	this->puerto = pPuerto;
	this->manejadorConexionEvento = pManejador;
	this->sockfd = -1;
}

Conexion::~Conexion() { }

void Conexion::Iniciar() throw (Excepcion&) {
	try {
		// @todo: si el manjeador de nueva sesion no esta definido lanza una 
		// excepcion.
		if (NULL == this->manejadorConexionEvento)
			throw Excepcion("No hay definido un manejador de evento de"
					" nueva sesion.");

		// Configuro la conexion.
		if (-1 == ((this->sockfd = socket(AF_INET, SOCK_STREAM, 0)))) {
			string msg = "No se puede iniciar el socket. ";
			msg += strerror(errno);

			throw Excepcion(msg);
		}
		bzero((char *)&serv_addr, sizeof (serv_addr));
		this->serv_addr.sin_family = AF_INET;
		this->serv_addr.sin_addr.s_addr = INADDR_ANY;
		this->serv_addr.sin_port = htons(atoi(this->puerto.c_str()));

		if (-1 == bind(sockfd, (struct sockaddr *)&serv_addr,
				sizeof (serv_addr))) {
			string msg = "No se puede iniciar el puerto. ";
			msg += strerror(errno);
			throw Excepcion(msg);
		}

		if (-1 == listen(sockfd, 5)) {
			string msg = "Error en listen. ";
			msg += strerror(errno);
			throw Excepcion(msg);
		}

		this->conexionFinalizada = false;

		// Inicio la ejecucion.
		Thread::Iniciar();
	}
	catch (Excepcion& pEx) {
		string msg = "No se puede inicar la conexion (Puerto: ";
		msg += this->puerto;
		msg += ").";
		pEx.expandirMensaje(msg);
		throw;
	}
}

void Conexion::Finalizar() throw (Excepcion&) {
	try {
		// Finalizo el socket de conexion.
		this->conexionFinalizada = true;
		if (-1 == shutdown(this->sockfd, SHUT_RDWR)) {
			this->conexionFinalizada = false;
			string msg = "Fallo al finalizar la conexion. ";
			msg += strerror(errno);
			throw Excepcion(msg);
		}
		Thread::Finalizar();
	}
	catch (Excepcion& pEx) {
		string msg = "No se puede finalizar la conexion (Puerto: " +
				this->puerto + ").";
		pEx.expandirMensaje(msg);
		throw;
	}
}

void Conexion::tarea() {
	this->errorConexion = false;
	while (!this->conexionFinalizada && !this->errorConexion) {
	/* Escucho una conexion entrante.  En caso de haber iniciado una conexion
	 * entrante correctamente instancio la sesion y lo notifico al administrador
	 * de nuevas sesiones.
	 */
		struct sockaddr_in* cli_addr = (struct sockaddr_in*)new (struct sockaddr_in);
		socklen_t clilen = sizeof (cli_addr);
		int newsockfd = accept(this->sockfd, (struct sockaddr*)cli_addr, &clilen);

		if (ERROR_CONEXION != newsockfd) {
			// Llego un nuevo cliente. Instancio la sesion y notifico.
			Sesion* sesion = new Sesion(newsockfd, cli_addr);
			this->manejadorConexionEvento->eventoNuevaSesion((IEnviante*) this, sesion);
		}
		else {
			if (cli_addr)
				delete cli_addr;

			if (!this->conexionFinalizada) {
				/* La conexion no fue finalizada por el servidor.
				 * El presente es un error de conexion.
				 */
				string msg = "ERROR_CONEXION";
				this->manejadorConexionEvento->eventoErrorConexionInexistente((IEnviante*) this, msg);
				this->errorConexion = true;
			}
		}
	}
}